#include <iostream>
#include <fstream>
#include <string>
#include <map>
#include <list>
#include <sstream>
#include <time.h>
#include <vector>
#include <cstdlib>
#include <cstring>
#include <algorithm>
#include <climits>
#include <cfloat>
#include <cmath>
#include <iomanip>
#include "utils.h"

using namespace std;

long contBigrams = 0;
long contUniqueBigrams = 0;
long contUniqueUnigrams = 0;
const string UNIGRAM_FILE = "UNIGRAMS.txt";
const string OUTPUT_FILE = "output.csv";

const int V_WORDS = 2183393; // Cantidad de palabras distintas (lenth de unigrams). 2183393
const string INPUT_FILE = "test_v2.txt";//"test_v2" "testChico"
const string TRAIN_FILE = "train_v2.txt";//"train_v2" "trainChico";
const string MODEL_FILE = "MODELO.txt";//"MODELO" "MODELO_CHICO";

/*const int V_WORDS = 2183393; // Cantidad de palabras distintas (lenth de unigrams). 2183393
const string INPUT_FILE = "test_v2.txt";//"test_v2" "testChico"
const string TRAIN_FILE = "trainChico.txt";//"train_v2" "trainChico";
const string MODEL_FILE = "MODELO_MEDIO.txt";//"MODELO" "MODELO_CHICO";*/


std::vector<pair<string,string> > GetBigrams(std::string linea) {
    string separador = " ";
	std::string token;
	std::string token2;
	std::vector<pair<string,string> > vecBigrams;

	std::vector<std::string> lista = split(linea, ' ');

	for (std::vector<std::string>::iterator it = lista.begin(); it != lista.end(); ++it) {
		if (!containsLetterOrNumber(*it)) continue;
		token = *it;
		token2 = "";
		for (int i = 1; i < lista.end() - it; i++) {
			auto next = std::next(it, i);
			if (containsLetterOrNumber(*next)) {
				token2 = *next;
				break;
			}
		}

		if (token2 != "") {
			std::transform(token.begin(), token.end(), token.begin(), ::tolower);
			std::transform(token2.begin(), token2.end(), token2.begin(), ::tolower);
			vecBigrams.push_back(make_pair(token,token2));
		} else {
			vecBigrams.push_back(make_pair(token,"#end"));
			break;
		}
	}

	return vecBigrams;
}

std::vector<std::string> GetUnigrams(std::string linea) {
    string separador = " ";
	std::string token;
	std::vector<std::string> vecUnigrams;

	std::vector<std::string> lista = split(linea, ' ');

	for (std::vector<std::string>::iterator it = lista.begin(); it != lista.end(); ++it) {
		if (!containsLetterOrNumber(*it)) continue;
		token = *it;
		std::transform(token.begin(), token.end(), token.begin(), ::tolower);
		vecUnigrams.push_back(token);
	}

	return vecUnigrams;
}

void MakeModel() {

	cout << "Comienzo de creacion de modelo.." << endl;

	// Abre file de train
    ifstream archivo2(TRAIN_FILE);

    string linea;
    long contador = 0;
	std::multimap<string,string> bigrams;
	std::map<pair<string,string>,int> counts;
	std::map<string,int> unigrams;

    if (archivo2.fail())
        cerr << "Error al abrir el archivo" << endl;
    else {
        while (!archivo2.eof()) {
			getline(archivo2,linea);

			std::vector<std::string> listaUnigrams = GetUnigrams(linea);
			std::vector<pair<string,string> > lista = GetBigrams(linea);

			string token;
			string token2;
			for (std::vector<pair<string,string> >::iterator it = lista.begin(); it != lista.end(); ++it) {
				token = (*it).first;
				token2 = (*it).second;

				if (counts.count(make_pair(token,token2)) > 0) {
					counts[make_pair(token,token2)]++;
				} else {
					// No existe, lo inserto.
					bigrams.insert(std::pair<std::string, std::string>(token, token2));
					counts[make_pair(token,token2)] = 1;
					contador++;
					contUniqueBigrams++;
				}

				contBigrams++;
			}

			// Unigrams
			for (std::vector<std::string>::iterator it = listaUnigrams.begin(); it != listaUnigrams.end(); ++it) {
				string token = *it;

				if (unigrams.count(token) > 0) {
					unigrams[token]++;
				} else {
					// No existe, lo inserto.
					unigrams[token] = 1;
					contUniqueUnigrams++;
				}
			}
        }

        // Escribe el archivo modelo
		escribirArchivo(MODEL_FILE, bigrams, counts);
		escribirArchivo(UNIGRAM_FILE, unigrams);
		archivo2.close();
		bigrams.clear();
		counts.clear();

		cout << "Modelo generado" << endl << endl;
		cout << "Cantidad de bigramas: " << contBigrams << endl;
		cout << "Cantidad de bigramas distintos: " << contUniqueBigrams << endl;
		cout << "Cantidad de unigramas distintos: " << contUniqueUnigrams << endl;
	}
}

void LoadModel(std::multimap<string,string> &bigrams, std::map<pair<string,string>,int> &counts) {
	cout << "Comienzo de carga de modelo.." << endl;

	ifstream model(MODEL_FILE);

    string linea;
	std::vector<std::string> lista;

    if (model.fail())
        cerr << "Error al cargar el modelo." << endl;
    else {
    	string token;
    	string token2;
    	int count;

        while (getline(model,linea)) {
			lista = split(linea, '|');
			token = lista[0];
			token2 = lista[1];
			count = atoi(lista[2].c_str());

			// Rearmo los maps
			bigrams.insert(std::pair<std::string, std::string>(token, token2));
			counts[make_pair(token,token2)] = count;
		}

		model.close();
		cout << "Modelo cargado" << endl << endl;
	}
}

void LoadUnigrams(std::map<string,int> &unigrams) {
	cout << "Comienzo de carga de unigramas.." << endl;

	ifstream model(UNIGRAM_FILE);

    string linea;
	std::vector<std::string> lista;

    if (model.fail())
        cerr << "Error al cargar los unigramas." << endl;
    else {
    	string token;
    	int count;
        while (getline(model,linea)) {
			lista = split(linea, '|');
			token = lista[0];
			count = atoi(lista[1].c_str());

			// Rearmo los unigramas
			unigrams.insert(std::pair<std::string, int>(token, count));
		}

		model.close();
		cout << "Unigramas cargados" << endl << endl;
	}
}

double MLE_Probability(string token, string token2, std::map<pair<string,string>,int> &counts, std::map<string,int> &unigrams) {
	/*
	 * P(wi|wi-1) = (C(wi-1|wi) + 1)*C(wi) / (C(wi-1) + V)
	 */

	 int countUni = (unigrams.count(token) == 0) ? 0 : unigrams.at(token);
	 int countBi = (counts.count(std::make_pair(token,token2)) == 0) ? 0 : counts.at(std::make_pair(token,token2));
	 int countUnitoken2 = (unigrams.count(token2) == 0) ? 0 : unigrams.at(token2);

	 return (double)((countBi * countUnitoken2) + 1) / (double)(countUni + V_WORDS);
}

/*// token _______ token2
double viterbi_Model(string token, string token2, std::multimap<string,string> &bigrams,std::map<pair<string,string>,int> &counts, std::map<string,int> &unigrams) {

	 std::map<string,double> vProbabilidades;
	 double probaToken_medio;

	 double maxProba = -1;
	 string maxKey;

	 std::pair<std::multimap<string,string>::iterator, std::multimap<string,string>::iterator> primerosBigramas;
	 primerosBigramas = bigrams.equal_range(token);

	 int contBigramasToken = 1;
	 //primero calculo las probabilidades de token / medio
	 /////////////////////////////////////////////////////////////////////
	 for (std::multimap<string,string>::iterator it=primerosBigramas.first; it!=primerosBigramas.second; ++it) {
		 string token_medio = it->second;
		 int countBiToken_token_medio = (counts.count(std::make_pair(token,token_medio)) == 0) ? 0 : counts.at(std::make_pair(token,token_medio));
		 //vProbabilidades.insert(std::pair<std::string, int> (token_medio,countBiToken_token_medio));

		 vProbabilidades.insert(make_pair(token_medio,countBiToken_token_medio));
		 contBigramasToken = contBigramasToken + countBiToken_token_medio;
	 }

	 for (std::map<string,double>::iterator itProba=vProbabilidades.begin(); itProba!=vProbabilidades.end(); ++itProba) {
		// cout << itProba->second<< " -> ";
		 itProba->second = (itProba->second /contBigramasToken);
	   //  cout << itProba->second<< endl;
	 }
	 /////////////////////////////////////////////////////////////////////

	 //para cada token/Medio busco los bigramas Medio/X, y calculo las probabilidades con X = token2
	 for (std::multimap<string,string>::iterator it=primerosBigramas.first; it!=primerosBigramas.second; ++it) {
		string token_medio = it->second;

	 	std::pair<std::multimap<string,string>::iterator, std::multimap<string,string>::iterator> segundosBigramas;
	 	segundosBigramas = bigrams.equal_range(token_medio);
	 	int countBiToken_medioToken2 = (counts.count(std::make_pair(token_medio,token2)) == 0) ? 0 : counts.at(std::make_pair(token_medio,token2));
	 	//cout<< token_medio << "-"<< token2 <<"Cantidad: "<< countBiToken_medioToken2<< endl;

	 	int contBigramasToken_medio = 1;
	 	//recorre para cada Medio/X los bigramas y los cuenta
	 	for (std::multimap<string,string>::iterator itMedio=segundosBigramas.first; itMedio!=segundosBigramas.second; ++itMedio) {
	 		string token_ultimo = (*itMedio).second;
	 		int cant_medio_siguiente = (counts.count(std::make_pair(token_medio,token_ultimo)) == 0) ? 0 : counts.at(std::make_pair(token_medio,token_ultimo));
	 		contBigramasToken_medio = contBigramasToken_medio + cant_medio_siguiente;

	 		//cuento los unigramas de esa palabra
	 		int countUni = (unigrams.count(token_medio) == 0) ? 0 : unigrams.at(token_medio);
	 		probaToken_medio= countUni / V_WORDS;
	 		//cout << token_medio<<"|" << token_ultimo << endl;
	 	}
	 	//cout << "contador "<< contBigramasToken_medio << endl;

	 	//a la probabilidad ye agrego un +0.1 para que no de 0
	 	double proba_token_medio_token2 = (((countBiToken_medioToken2 + 0.01) / contBigramasToken_medio)) * probaToken_medio;

	    std::map<string, double>::iterator itProba = vProbabilidades.find(token_medio);
	    //cout << token << " " << itProba->second<< token_medio <<" --2> ";
	    itProba->second = (itProba->second * proba_token_medio_token2);
	   //cout << itProba->second<< " " <<token2<< endl;
	    if (itProba->second > maxProba){
			maxProba=itProba->second;
			maxKey = itProba->first;
	    }
	 }

	 cout << "MAXIMA PROBABILIDAD " << token << " - " << maxKey <<  " - " <<  token2 << endl;

	 return 0;
}*/

size_t GetWordPosition(string phrase, std::vector<pair<string,string> > &bigrams, std::map<pair<string,string>,int> &counts, std::map<string,int> &unigrams, string &leftToken, string &rightToken) {

	double minCount = INT_MAX;
	string token;
	string token2;
	double prob;

	// Bigrama menos comun
	for (std::vector<pair<string,string> >::iterator it = bigrams.begin(); it != bigrams.end(); ++it) {
		token = (*it).first;
		token2 = (*it).second;
		prob = MLE_Probability(token,token2,counts,unigrams);

		if (prob < minCount) {
			leftToken = token;
			rightToken = token2;
			minCount = prob;
		}
	}

	return ci_find(phrase, leftToken + " ") + leftToken.length();
}

string GetMissingWord(std::multimap<string,string> &bigrams, std::map<pair<string,string>,int> &counts, std::map<string,int> &unigrams, string leftToken, string rightToken, std::map<string,string> &mostProbable) {
	/* leftToken __value__ rightToken */

	if (mostProbable.count(leftToken) > 0)
		return mostProbable.at(leftToken);

	string missingWord = " ";
	double maxProb = -1;
	double prob;
	string value;

	std::pair<std::multimap<string,string>::iterator, std::multimap<string,string>::iterator> ret;
	ret = bigrams.equal_range(leftToken);

	// Itero todas los valores para una clave 'leftToken'
	for (std::multimap<string,string>::iterator it=ret.first; it!=ret.second; ++it) {
		value = it->second;

	    prob = MLE_Probability(leftToken, value, counts, unigrams) + MLE_Probability(value, rightToken, counts, unigrams);
		//prob = (counts.count(std::make_pair(leftToken,value)) == 0) ? -1 : counts.at(std::make_pair(leftToken,value));

		if (prob > maxProb) {
			missingWord = value;
			maxProb = prob;
		}
	}

	mostProbable.insert(std::pair<std::string, std::string>(leftToken, missingWord));
	return missingWord;
}

void Predict(string &phrase, std::multimap<string,string> &bigrams, std::map<pair<string,string>,int> &counts, std::map<string,int> &unigrams, std::map<string,string> &mostProbable) {
	int position = buscarComillasSueltas(phrase);

	if (position >= 0) {
		if (position == phrase.length())
			phrase.insert(position, " \"\"");
		else
			phrase.insert(position, "\"\" ");
	} else {
		string leftToken, rightToken = "";
		std::vector<pair<string,string> > bigramsInPhrase = GetBigrams(phrase);
		position = GetWordPosition(phrase, bigramsInPhrase, counts, unigrams, leftToken, rightToken);

		//viterbi_Model(leftToken,rightToken,bigrams,counts,unigrams);

		//clock_t time = clock();
		string word = GetMissingWord(bigrams, counts, unigrams, leftToken, rightToken, mostProbable);

		//time = (clock()-time) / 1000;
		//cout << "Tiempo Missing Word (" << leftToken << ") : " << time << " segundos" << endl;

		phrase.insert(position + 1, word + " ");
	}
}

void ReadInput(string filename, std::multimap<string,string> &bigrams, std::map<pair<string,string>,int> &counts, std::map<string,int> &unigrams) {
	cout << "Comienzo de generacion de output.." << endl;

	ifstream input(filename);
	string row;

	std::map<string,string> mostProbable;

	ofstream ficheroSalida;
	ficheroSalida.open(OUTPUT_FILE);
	ficheroSalida << "\"id\"" << ',' << "\"sentence\"" << '\n';

	if (input.fail())
        cerr << "Error al abrir el archivo" << endl;
    else {
		getline(input,row);
		string id;
		string phrase;
		while (getline(input,row)) {
			std::istringstream s(row);
			getline(s, id, ',');
			getline(s, phrase, '\n');

			// Quito las comillas
			phrase = phrase.substr(1, phrase.length()-2);

			//cout << id << " " << phrase << endl;
			Predict(phrase, bigrams, counts, unigrams, mostProbable);

			//cout << id << ",\"" << phrase << "\"" << '\n';
			ficheroSalida << id << ",\"" << phrase << "\"" << '\n';
		}

		ficheroSalida.close();
		cout << "Output generado" << endl << endl;
	}
	mostProbable.clear();
}

void DisplayMenu() {
	cout << endl;
	cout << "=====================" << endl;
	cout << "1- Generar Modelo" << endl;
	cout << "2- Cargar Modelo" << endl;
	cout << "3- Predecir" << endl;
	cout << "0- Salir" << endl;
	cout << "=====================" << endl;
}

int main() {

	// Modelo persistente
	std::multimap<string,string> bigrams;
	std::map<pair<string,string>,int> counts;
	std::map<string,int> unigrams;

	int choice;
	DisplayMenu();
	cin >> choice;

	while (choice != 0) {
		clock_t time = clock();

		switch (choice) {
			case 1:
				MakeModel();
				break;
			case 2:
				LoadModel(bigrams, counts);
				LoadUnigrams(unigrams);

				//bigrams.clear();
				//counts.clear();
				//unigrams.clear();
				break;
			case 3:
				ReadInput(INPUT_FILE, bigrams, counts, unigrams);
				break;
			default:
				DisplayMenu();
				cin >> choice;
				continue;
		}

		time = (clock()-time) / 1000;
		cout << "Tiempo: " << time << " segundos" << endl;

		DisplayMenu();
		cin >> choice;
	}

	bigrams.clear();
	counts.clear();
	unigrams.clear();

	return 0;
}
